﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Frogs.Stability.CircuitBreaker.Behaviors;
using Frogs.Stability.CircuitBreaker.Behaviors.Functions;
using Frogs.Stability.CircuitBreaker.Exceptions;
using Frogs.Stability.LeakyBucket;

namespace Frogs.Stability.CircuitBreaker
{
	/// <summary>
	/// Default implementation of Frogs.Stability.CircuitBreaker.IFunctionCircuitBreaker
	/// </summary>
	public class DefaultFunctionCircuitBreaker : IFunctionCircuitBreaker
	{
		object _mutex = new object();

		ILeakyBucket LeakyBucket { get; set; }

		IFunctionCircuitBehavior _circuitBehavior = new ClosedBehavior();
		IFunctionCircuitBehavior CircuitBehavior
		{
			get
			{
				lock (this._mutex)
				{
					var value = this._circuitBehavior;
					return value;
				}
			}
			set
			{
				lock (this._mutex)
				{
					this._circuitBehavior = value;
				}
			}
		}

		/// <summary>
		/// Initializes a new instance of Frogs.Stability.CircuitBreaker.DefaultFunctionCircuitBreaker
		/// </summary>
		public DefaultFunctionCircuitBreaker(ILeakyBucket leakyBucket)
		{
			this.LeakyBucket = leakyBucket;
			this.LeakyBucket.WaterLevel.Subscribe(state =>
			{
				switch (state)
				{
					case BucketState.Full:
						this.SetOpenState();
						break;
					case BucketState.HalfDrained:
						this.SetHalfOpenState();
						break;
					case BucketState.Empty:
						SetClosedState();
						break;
					default:
						break;
				}
			});
		}

		void SetOpenState()
		{
			var openBehavior = new OpenBehavior();
			this.CircuitBehavior = openBehavior;
		}

		void SetHalfOpenState()
		{
			var halfOpenBehavior = new HalfOpenBehavior();
			halfOpenBehavior.ExecutionResult.Subscribe(result =>
			{
				switch (result)
				{
					case Behaviors.ExecutionResult.Success:
						this.ResetCircuit();
						break;
					case Behaviors.ExecutionResult.Fail:
						this.TripCircuit();
						break;
					default:
						break;
				}
			});
			this.CircuitBehavior = halfOpenBehavior;
		}

		void SetClosedState()
		{
			var closedBehavior = new ClosedBehavior();
			closedBehavior.ExecutionResult.Subscribe(result =>
			{
				if (result == Behaviors.ExecutionResult.Fail)
				{
					LeakyBucket.AddSingleDrop();
				}
			});
			this.CircuitBehavior = closedBehavior;
		}

		/// <summary>
		/// Attempt to perform the supplied function within the constraints of a circuit breaker.
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="function"></param>
		/// <returns></returns>
		public T Do<T>(Func<T> function)
		{
			T value = this.CircuitBehavior.DoFunction(function);
			return value;
		}

		/// <summary>
		/// Reset the state of the circuit breaker to closed
		/// </summary>
		public void ResetCircuit()
		{
			this.LeakyBucket.Empty();
		}

		/// <summary>
		/// Trips the circuit breaker, setting the status to open
		/// </summary>
		public void TripCircuit()
		{
			this.LeakyBucket.FillUp();
		}

		/// <summary>
		/// Gets the state of the circuit breaker
		/// </summary>
		/// <returns></returns>
		public CircuitState QueryState()
		{
			var tmp = this.CircuitBehavior;
			return tmp.GetStatus();
		}
	}
}
